AI Agent 是如何记住上下文的?从短期记忆到长期记忆
更新: 10/15/2026字数: 0 字 时长: 0 分钟
前面几篇文章,我们已经了解了 AI Agent 如何调用工具、如何通过 MCP 连接外部系统,以及如何利用权限控制和 Sandbox 限制操作风险。
但还有一个非常关键的问题:
如果 AI Agent 每次只能看到当前输入,它怎么记得之前讨论过什么?又怎么知道一个任务已经执行到哪一步?
例如,你让 AI 编程助手完成以下任务:
- 先分析项目中的登录模块。
- 找出登录失败的原因。
- 修改相关代码。
- 运行测试并修复问题。
- 最后总结修改内容。
这不是一次简单的问答,而是一个持续执行的任务。
Agent 在第 4 步时,需要知道之前分析出了什么问题、修改过哪些文件、测试失败的原因是什么。如果这些信息全部丢失,它就可能重复分析、重复修改,甚至推翻之前已经做出的决定。
这就涉及 AI Agent 的上下文管理与记忆机制。
需要先明确一个概念:
大模型并不会因为处理过一条消息,就自动把这条消息永久保存下来。Agent 所谓的“记住”,通常是通过上下文传递、外部存储、信息检索和状态管理等机制共同实现的。
本文将从实际开发的角度,讲清楚 AI Agent 的记忆原理、短期记忆与长期记忆的区别,以及如何设计一套可用于生产环境的记忆系统。
一、为什么 AI Agent 需要记忆?
先看一个普通的大模型调用。
const answer = await llm.generate({
prompt: "帮我分析一下登录模块",
});模型根据当前提供的输入生成回答。如果后续请求没有包含之前的对话内容或相关信息,模型就不一定知道之前分析过什么。
而在 Agent 系统中,任务通常具有连续性。
例如:
用户:帮我修复登录失败的问题。
Agent:我先检查登录接口和相关组件。
Agent:发现登录请求返回 401,继续检查认证逻辑。
Agent:发现 Token 读取路径存在问题,准备修改代码。
Agent:代码已修改,正在运行测试。
Agent:测试通过,问题已修复。如果每次调用模型时都只传入最后一句话,模型就无法可靠地理解前面的执行过程。
因此,Agent 需要管理多种信息:
- 对话历史:用户之前说过什么,已经达成哪些共识。
- 任务状态:当前执行到哪一步,还有哪些工作未完成。
- 执行记录:调用过哪些工具,返回了什么结果。
- 项目知识:项目使用什么技术栈、有哪些约定。
- 长期偏好:用户明确要求持续遵守的编码规范或工作习惯。
这些信息不一定全部放在同一个地方。不同类型的数据,需要采用不同的保存和读取方式。
二、AI Agent 的记忆到底存在哪里?
从工程实现来看,Agent 的记忆通常不是单一的功能,而是一组协同工作的机制。
短期上下文
当前对话、最近几轮消息、工具执行结果
任务状态
执行步骤、待办事项、成功与失败记录
长期记忆
用户偏好、历史经验、经过整理的事实
外部知识
项目文件、知识库、数据库、文档索引Agent 根据当前任务选择相关信息,再将整理后的上下文交给模型。以上是常见的工程划分,并非所有框架都采用完全相同的架构。
这里需要区分两个概念:
记忆存储负责保存信息;上下文构建负责决定本次模型调用需要看到哪些信息。
即使系统保存了完整的历史记录,也不意味着每次都要把所有记录发送给模型。
因为模型的上下文窗口有容量限制,而且无关信息越多,越容易增加成本和干扰模型对当前任务的判断。
因此,Agent 需要的不只是“能够保存”,还需要“能够找到、筛选和组织”。
三、短期记忆:让 Agent 记住当前任务
短期记忆(Short-term Memory)主要用于维护当前对话或任务中的信息。
例如,用户连续提出以下要求:
用户:帮我分析登录模块。
Agent:登录逻辑位于 src/auth/login.ts。
用户:修复刚才发现的问题。
Agent:我会继续检查 src/auth/login.ts,并修复相关逻辑。
用户:测试通过了吗?
Agent:测试已经通过。最后一句“测试通过了吗”,必须结合前面的对话才能理解。
3.1 短期记忆通常保存什么?
在 Agent 系统中,短期记忆可能包括:
- 当前会话的用户消息和助手回复;
- 工具调用请求及返回结果;
- 当前任务的目标与执行计划;
- 已完成的步骤和待处理事项;
- 最近读取的文档、文件和其他中间结果。
其中,对话历史和任务状态不是一回事。
对话历史记录发生过什么,任务状态则明确当前应该做什么。
例如:
{
"task": "修复登录失败问题",
"status": "running",
"completedSteps": [
"检查登录接口",
"定位 Token 读取问题",
"修改认证逻辑"
],
"pendingSteps": [
"运行测试",
"检查修改是否引入新问题"
]
}即使后续对话历史被压缩,只要任务状态被正确保存,Agent 仍然可以知道接下来应该做什么。
3.2 短期记忆存在哪里?
常见实现有以下几种:
| 存储方式 | 特点 | 适用场景 |
|---|---|---|
| 内存变量 | 实现简单,进程退出后通常丢失 | Demo、短任务 |
| SQLite | 轻量、支持本地持久化 | 本地 AI 助手 |
| PostgreSQL / MySQL | 适合持久化与多用户管理 | 企业应用 |
| Redis | 适合快速访问短期状态 | 高并发、临时状态 |
| 专门的会话存储 | 封装历史记录与会话管理 | Agent 框架 |
例如,一个简单的 TypeScript 应用可以用 Map 保存会话消息:
type Message = {
role: "user" | "assistant";
content: string;
};
const sessions = new Map<string, Message[]>();
function addMessage(
sessionId: string,
message: Message,
) {
const history = sessions.get(sessionId) ?? [];
history.push(message);
sessions.set(sessionId, history);
}每次调用模型前,应用就可以通过 sessionId 获取对应的历史记录。
但这只是演示。Map 无法自动解决服务重启、多个服务器实例之间的状态共享、会话过期和数据隔离等问题。生产环境通常需要持久化存储。
3.3 为什么还需要任务状态?
假设 Agent 正在修改一个包含几十个文件的大型项目。
如果只保存聊天记录,模型每次都需要从大量对话中重新推断任务进度,容易遗漏已经完成的步骤。
更可靠的方式是将任务状态单独持久化:
type TaskState = {
taskId: string;
goal: string;
currentStep: string;
completedSteps: string[];
pendingSteps: string[];
lastError?: string;
};当任务执行到下一步时,Agent Runtime 更新状态,并在后续调用中读取。
这样,即使程序意外退出,只要状态和相关执行记录已经可靠保存,就可以尝试恢复任务。
这也是许多 Agent 框架使用状态持久化、Checkpoint(检查点)等机制的原因。LangGraph 就将短期记忆与线程状态结合,并通过 Checkpointer 保存和恢复执行状态。
四、长期记忆:让 Agent 跨会话记住重要信息
短期记忆解决的是“当前任务还记不记得”。
但如果用户今天告诉 AI:
我开发项目时使用 TypeScript,不允许使用
any,代码注释统一使用中文。
第二天重新打开应用,用户希望 AI 仍然遵守这些要求。
如果每次都必须重新说明,就需要长期记忆。
长期记忆(Long-term Memory)是能够跨越单次对话或任务持续保存,并在未来需要时重新取用的信息。
4.1 长期记忆可以保存什么?
例如:
{
"userPreferences": {
"language": "zh-CN",
"codeLanguage": "TypeScript",
"allowAny": false,
"commentLanguage": "中文"
},
"projectFacts": {
"framework": "Next.js",
"database": "MySQL",
"packageManager": "pnpm"
}
}这些信息被保存后,下一次用户要求生成代码时,系统就可以根据当前任务读取相关偏好,并将其加入模型上下文。
需要注意,长期记忆不应该成为一份无限增长的个人档案。只应保存对未来任务确实有帮助、具有合理保存依据的信息,并允许用户查看、修改和删除。
4.2 长期记忆并不等于向量数据库
很多人一提到 AI 记忆,就会想到向量数据库。
实际上,长期记忆可以采用多种存储方式:
| 存储方式 | 适合场景 | 典型方式 | 特点 |
|---|---|---|---|
| 结构化数据库 | 存储明确、可直接查询的信息,如用户偏好、项目配置、任务状态、权限设置 | MySQL、PostgreSQL、SQLite | 格式固定、字段清晰;支持精确查询;适合频繁更新和事务操作 |
| 文件/文档存储 | 保存项目约定、历史任务总结、操作指南等便于人工查看和维护的内容 | Markdown、JSON、对象存储 | 人类可读、便于编辑和版本管理;结构灵活;检索依赖路径或全文搜索 |
| 向量数据库 | 将文本转为向量,根据语义相似度检索相关内容 | pgvector、Milvus、专用向量数据库 | 支持语义检索,不依赖关键词匹配;适合问答、推荐、RAG;需配合 Embedding 模型 |
举个例子:
用户问:“按照之前的项目规范生成一个 API。”
- 项目使用什么数据库,可以直接查结构化配置。
- TypeScript 编码约定,可以读取保存的规则。
- 之前讨论过的类似 API 设计方案,可以通过关键词或语义检索查找。
这三类信息不需要强行塞进同一种存储系统。
记忆系统的关键不是使用了什么数据库,而是能否正确保存、检索、更新和使用信息。
五、Agent 是如何从历史信息中找到需要的记忆的?
保存信息只是第一步。
真正困难的是:当历史记录越来越多时,如何找到与当前任务最相关的信息?
假设用户说:
按照上次的方式,继续优化这个项目的登录页面。
系统可能已经积累了大量历史信息,包括其他页面的设计讨论、数据库配置、代码错误和部署记录。
如果把所有内容都塞进上下文,不仅浪费 Token,还可能让模型受到无关信息干扰。
因此,Agent 通常需要一个记忆检索流程。
当前用户请求:“按照上次的方式优化登录页面”
解析任务与检索条件:识别项目、页面、历史设计偏好等关键词
检索记忆:查询结构化数据、历史记录或向量索引
筛选与组装上下文:保留相关、有效、可信的信息
LLM 生成下一步行动:结合当前请求和检索结果继续执行5.1 常见的记忆检索方式
| 检索方式 | 原理 | 适用场景 |
|---|---|---|
| 精确查询 | 按用户 ID、项目 ID、任务 ID 等查询 | 项目配置、任务状态 |
| 关键词检索 | 根据词语匹配相关内容 | 文档标题、错误日志 |
| 向量检索 | 根据语义相似度查找内容 | 历史讨论、经验总结 |
| 混合检索 | 结合关键词、语义与业务条件 | 复杂知识库 |
| 时间或优先级过滤 | 优先选择近期或权威信息 | 有版本变化的配置与规则 |
例如,查询项目编码规范时,系统应该优先读取当前项目明确维护的 AGENTS.md 或其他规范文件,而不是单纯依靠语义相似度,从几个月前的聊天记录中猜测规范。
这是一个很重要的设计原则:
检索结果的相关性很重要,但信息的来源、时效性和可信度同样重要。
六、当上下文太长时,Agent 如何处理?
即使模型的上下文窗口越来越大,也不意味着可以无限制地保存并发送所有历史记录。
长上下文可能带来几个问题:
- 输入 Token 增多,调用成本上升。
- 历史工具输出占据大量上下文。
- 过时的结论可能与最新状态冲突。
- 无关内容会干扰当前任务。
- 重要信息可能被大量细节淹没。
因此,Agent 通常需要对上下文进行管理。
6.1 滑动窗口:只保留最近的消息
最简单的方式是只保留最近几轮对话。
function getRecentMessages<T>(
messages: T[],
limit: number,
): T[] {
return messages.slice(-limit);
}优点是实现简单,适合短对话。
缺点也很明显:较早但仍然重要的信息可能被删除。
例如,用户在对话开始时明确指定“不能修改数据库结构”,如果该消息被裁剪掉,Agent 就可能失去这一约束。
所以,不能只保留最近消息,还应该单独保留关键指令、权限约束和任务状态。
6.2 历史摘要:把大量对话压缩成重点
另一种方式是对较早的历史进行总结。
例如,原始对话有几十轮:
检查登录接口……
读取 Token 逻辑……
修改认证组件……
运行测试……
发现一个边界条件错误……
再次修改……可以将已经确认的内容整理为摘要:
任务:修复登录失败问题。
已确认:
1. 问题出在 Token 读取逻辑。
2. 已修改 src/auth/login.ts。
3. 第一次测试发现一个边界条件错误。
当前进度:
等待重新运行测试。
约束:
不修改数据库结构,不更改公共 API。这样,模型无需每次读取全部历史,也能掌握关键进展。
不过,摘要本身可能遗漏细节或产生错误,因此重要事实最好仍然能追溯到原始记录。
6.3 工具输出清理:不要把所有结果都留在上下文里
AI 编程 Agent 经常会执行命令:
npm test假设测试输出有数千行,但真正有用的信息只有:
FAIL src/auth/login.test.ts
Expected status: 200
Received status: 401如果每次都将完整日志传给模型,可能很快占满上下文。
更好的方式是:
- 将完整日志保存到文件或日志系统;
- 提取错误摘要和相关堆栈;
- 将摘要传入模型;
- 需要进一步分析时,再读取对应日志片段。
这种方式能够兼顾上下文效率和信息可追溯性。
6.4 上下文压缩与长期记忆有什么区别?
这两个概念经常被混淆。
| 机制 | 目的 | 典型操作 |
|---|---|---|
| 上下文压缩 | 减少当前任务所需携带的信息量 | 摘要、裁剪、清理冗余输出 |
| 长期记忆 | 将未来可能有用的信息保存下来 | 保存偏好、事实、经验并在未来检索 |
| Checkpoint | 保存任务执行状态,便于恢复 | 保存步骤、状态和中间结果 |
三者可以一起使用,但解决的问题不同。OpenAI Agents SDK 提供 Session 机制来维护会话历史;其相关开发资料也介绍了通过裁剪与压缩管理长期对话上下文的方法。
七、动手实现:一个简单的 Agent 记忆系统
理解了原理之后,我们可以用 TypeScript 实现一个简化版本。
这里不引入向量数据库,也不依赖特定 Agent 框架,而是先把核心流程搭建出来。
整体思路是:
- 根据
sessionId读取当前会话历史。 - 根据
userId读取用户长期偏好。 - 组装当前任务需要的上下文。
- 调用模型。
- 保存新的消息与必要状态。
7.1 定义记忆类型
type Message = {
role: "user" | "assistant";
content: string;
};
type UserMemory = {
language: string;
codeLanguage: string;
codingRules: string[];
};
type AgentContext = {
userId: string;
sessionId: string;
history: Message[];
memory: UserMemory | null;
};这里把会话历史与长期偏好分开管理,避免将所有信息混在一个数组里。
7.2 定义记忆存储接口
interface MemoryStore {
getHistory(
sessionId: string,
): Promise<Message[]>;
saveMessage(
sessionId: string,
message: Message,
): Promise<void>;
getUserMemory(
userId: string,
): Promise<UserMemory | null>;
}通过接口隔离存储实现,后续可以将内存存储替换为 PostgreSQL、MySQL 或其他持久化方案,而不需要重写 Agent 的全部业务逻辑。
7.3 组装上下文
function buildContext(
userId: string,
sessionId: string,
history: Message[],
memory: UserMemory | null,
): AgentContext {
return {
userId,
sessionId,
history,
memory,
};
}这只是基础的数据组装。生产系统通常还会增加消息裁剪、摘要、检索结果排序、Token 预算与敏感信息过滤。
7.4 执行一轮对话
async function runAgent(
store: MemoryStore,
userId: string,
sessionId: string,
userInput: string,
): Promise<string> {
// 读取当前会话历史
const history = await store.getHistory(sessionId);
// 读取用户长期偏好
const memory = await store.getUserMemory(userId);
// 组装当前任务上下文
const context = buildContext(
userId,
sessionId,
history,
memory,
);
// 调用模型:此处为伪代码
const answer = await callModel({
userInput,
context,
});
// 保存当前对话
await store.saveMessage(sessionId, {
role: "user",
content: userInput,
});
await store.saveMessage(sessionId, {
role: "assistant",
content: answer,
});
return answer;
}其中 callModel 是占位函数,需要接入实际的大模型 SDK;MemoryStore 也需要提供具体实现。这段代码用于展示流程,并不是可以直接运行的完整应用。
真实的 Agent 还需要考虑并发写入、消息顺序、模型失败后的重试,以及工具调用过程中的中间状态持久化。
另外,保存用户消息与助手回复时,最好使用事务或具有明确顺序的持久化机制,避免只保存成功了一半。
7.5 什么时候应该写入长期记忆?
不建议把每一条对话都自动变成长期记忆。
例如:
用户:这次先用蓝色做一版看看。这可能只是当前设计任务的临时决定,并不代表用户今后所有项目都喜欢蓝色。
相比之下:
用户:以后生成项目代码时,都使用 TypeScript,
不要使用 any,并且代码注释使用中文。这更像一条适合保存的长期偏好。
一个实用的记忆写入流程是:
发现候选记忆:从用户指令、反馈或任务结果中提取信息
判断是否值得保存:是否稳定、可复用、明确且对未来任务有帮助?
校验与更新:检查来源、冲突、有效期和用户授权
持久化:保存到适当的存储,并支持后续修改或删除这个过程可以由应用逻辑执行,也可以让模型辅助提取候选信息,但最终写入策略应由系统控制。
八、生产级记忆系统需要注意什么?
简单 Demo 可以用数组和 Map,但在真正的 AI 产品中,记忆系统还要考虑以下问题。
8.1 多用户隔离
不能仅凭一个可猜测的 sessionId 就允许访问会话。
后端应当验证当前用户是否有权读取对应会话,并确保长期记忆按照用户、组织或项目进行命名空间隔离。
8.2 记忆可能过期
用户的项目技术栈可能从 React 迁移到 Vue,数据库可能更换,旧的部署方式也可能不再适用。
因此,长期记忆应当支持更新时间、来源和有效性管理。对于关键项目配置,最好优先读取当前权威配置,而不是依赖旧的聊天记录。
8.3 记忆内容不一定可信
历史对话、外部文档和检索结果可能包含错误信息,甚至包含恶意指令。
因此,记忆应该作为待评估的信息,而不是自动获得最高优先级的系统指令。检索到的内容也不能绕过现有的权限控制。
8.4 隐私与删除机制
长期记忆可能保存用户偏好、项目资料或其他敏感内容。系统应遵循数据最小化原则,避免保存不必要的秘密、访问凭据或敏感个人信息。
同时,应支持用户查看、修改和删除记忆,并让删除操作能够覆盖相关索引、缓存或其他副本。
8.5 记忆需要评估效果
“保存了更多信息”并不意味着 Agent 一定会更聪明。
可以通过测试集评估:
- 是否能正确召回过去确认的信息;
- 是否错误地使用过期信息;
- 是否把不同用户的记忆混在一起;
- 是否能够遵守长期偏好;
- 是否因为无关记忆而生成错误结果。
这些指标比单纯统计记忆条数更有意义。
九、总结:AI Agent 的记忆是一套完整的工程机制
回到最开始的问题:AI Agent 是如何记住上下文的?
答案不是让大模型永久记住每一次对话,而是由 Agent Runtime 和存储系统共同管理信息,并在每次执行任务时将需要的内容重新提供给模型。
可以用下面这张表总结:
| 机制 | 解决的问题 |
|---|---|
| 短期记忆 | 记住当前对话与任务进度 |
| 长期记忆 | 跨会话保存可复用的信息 |
| 上下文检索 | 找到当前任务真正需要的内容 |
| 摘要与裁剪 | 控制上下文长度与调用成本 |
| Checkpoint | 保存执行状态并支持任务恢复 |
| 权限与数据治理 | 防止记忆泄露、越权访问和错误复用 |
真正可靠的 Agent,不是把所有历史信息都记下来,而是能够在正确的时间,找到可信且相关的信息,并把它们用于当前任务。
当记忆、工具调用、Agent Loop 和权限控制结合起来,AI 才能从一次性的问答工具,逐步发展成能够跨步骤执行任务的应用系统。